/* EasyWay Game Engine
 * Copyright (C) 2006 Daniele Paggi.
 *  
 * Written by: 2006 Daniele Paggi<dshnt@hotmail.com>
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */package org.easyway.objects.texture;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;

import org.easyway.annotations.editor.Creator;
import org.easyway.annotations.editor.Info;
import org.easyway.annotations.editor.Method;
import org.easyway.annotations.editor.Resource;
import org.easyway.interfaces.base.IDestroyable;
import org.easyway.interfaces.base.ITexture;
import org.easyway.objects.sprites2D.Mask;
import org.easyway.system.StaticRef;
import org.easyway.system.state.OpenGLState;
import org.easyway.utils.ImageUtils;
import org.easyway.utils.Utility;

/**
 * With this class you can use images in the game.<br>
 * <br>
 * example:<br>
 * ...<br>
 * Texture img = new Texture( "path" );<br>
 * new Sprite( img );<br>
 * ...<br>
 */
// @UEditor("Manage Textures")//
public class Texture implements IDestroyable, ITexture, Serializable {

    /**
     * generated version
     */
    private static final long serialVersionUID = 7472363451408935314L;

    /** indicates if the texture is destroyed or not */
    private boolean destroyed = false;

    /**
     * size of texture
     */
	public int width, height;

    /**
     * internal texture coordinates<br>
     * it's used to get the max border of texture...
     */
	public float xStart = 0, yStart = 0;

    /**
     * internal texture coordinates<br>
     * it's used to get the max border of texture...
     */
	public float xEnd = 1, yEnd = 1;

    /** the mask used to collision */
    protected Mask mask;

    /**
     * the class to manage the VRAM data
     */
    protected TextureID dataid;

    /**
     * this texture is a black\white texture used in the 2D graphic to creates
     * trasparents sections
     */
    public ITexture alphaMask;

    /**
     * the texture name
     */
    public String name;

    public boolean solid;

    // ------------------------------------------------------

    public Texture(TextureID data) {
        dataid = data;
        dataid.referenceCount++;
        solid = dataid.solid;
        width = data.width;
        height = data.height;
        xEnd = (float) width / (float) data.widthHW;
        yEnd = (float) height / (float) data.heightHW;
        StaticRef.textures.add(this);
    }

    /**
     * LOADS and crete a texture from a file<br>
     * 
     * @param file
     *            relative path
     */
    @Creator("load a solid texture")
    //
	public Texture(@Resource("path")
	String file) {
        this(new TextureID(file));
        name = file;
    }

    /**
     * create a new empty texture.
     * 
     * @param width
     *            size of texture
     * @param height
     *            size of texture
     */

    public Texture(int width, int height) {
        this(new TextureID(width, height, true));
        name = "$_TEXTURE";
    }

    /**
     * loads and create a texture from a file and cretes as trasparent the
     * section that have the color equal to the RGB valued spefied
     * 
     * @param file
     *            relative path
     * @param red
     *            red value to compare
     * @param green
     *            green value to compare
     * @param blue
     *            blue value to compare
     */
    @Creator("load a transparent texture")
    //
	public Texture(@Resource("path")
	String file, @Info("red")
	int red, @Info("green")
	int green, @Info("blue")
	int blue) {
        this(new TextureID(file, red, green, blue));
        name = file;
    }

    /**
     * LOADS and crete a texture from a file<br>
     * 
     * @param file
     *            relative path
     * @param textureName
     *            name of texture
     * 
     */
    public Texture(String file, String textureName) {
        this(new TextureID(file));
        name = textureName;
    }

    /**
     * loads and create a texture from a file and cretes as trasparent the
     * section that have the color equal to the RGB valued spefied
     * 
     * @param file
     *            relative path
     * @param red
     *            red value to compare
     * @param green
     *            green value to compare
     * @param blue
     *            blue value to compare
     * @param textureName
     *            name of texture
     */
    public Texture(String file, int red, int green, int blue, String textureName) {
        this(new TextureID(file, red, green, blue));
        name = textureName;
    }

    /**
     * creates a copy of an existent texture<br>
     * 
     * @param t
     *            original texture
     */

    public Texture(Texture t) {
        this.width = t.width;
        this.height = t.height;
        this.name = t.name;
        this.xStart = t.xStart;
        this.yStart = t.yStart;
        this.xEnd = t.xEnd;
        this.yEnd = t.yEnd;
        this.dataid = t.dataid;
        this.alphaMask = t.alphaMask;
    }

    /**
     * creates an emptiy texture and adds it to the game engine's texture list
     * 
     */
    public Texture() {
    }

    // ------------------------------------------------------

    /**
     * Blinds the image
     */
    public void bind() {
        dataid.bind();
        if (solid) {
            OpenGLState.disableAlphaTest();
        } else {
            OpenGLState.enableAlphaTest();
        }
    }

    // ------------------------------------------------------
    // ------------------------------------------------------

    /**
     * returns the texture's pixel in a ByteBuffer
     * 
     * EXAMPLE:<br>
     * ByteBuffer bb = getData();<br>
     * byte r, g, b;<br>
     * bb.rewind(); //<-- IMPORTANT!!<br>
     * try {<br>
     * while (true) {<br>
     * bb.mark();<br>
     * r = bb.get();<br>
     * g = bb.get();<br>
     * b = bb.get();<br>
     * bb.reset();<br>
     * bb.put((byte)(r+red));<br>
     * bb.put((byte)(g+green));<br>
     * bb.put((byte)(b+blue));<br>
     * bb.get(); // alpha<br> }<br> } catch (Exception e) {<br> }<br>
     * setData(bb);<br>
     * 
     * @retun texture's pixel
     */
    // bisogna tener conto di u,v
    public ByteBuffer getData() {
        return dataid.getData();
    }

    /**
     * sets the texture's pixel from a ByteBuffer
     * 
     * EXAMPLE:<br>
     * ByteBuffer bb = getData();<br>
     * byte r, g, b;<br>
     * bb.rewind(); //<-- IMPORTANT!!<br>
     * try {<br>
     * while (true) {<br>
     * bb.mark();<br>
     * r = bb.get();<br>
     * g = bb.get();<br>
     * b = bb.get();<br>
     * bb.reset();<br>
     * bb.put((byte)(r+red));<br>
     * bb.put((byte)(g+green));<br>
     * bb.put((byte)(b+blue));<br>
     * bb.get(); // alpha<br> }<br> } catch (Exception e) {<br> }<br>
     * setData(bb);<br>
     * 
     * @param data
     *            texture's pixel data
     */
    public void setData(ByteBuffer data) {
        dataid.setData(data);
    }

    public TextureID getTextureId() {
        return dataid;
    }

    // ------------------------------------------------------
    // ------------------------------------------------------
    // ------------------------------------------------------

    /**
     * indicates if the texture has a mask of collisions or not
     */
    public boolean isCollisionable() {
        if (mask != null) {
            return true;
        }
        return false;
    }

    /**
     * returns the mask of collisions
     * 
     * @return mask of collisions
     */
    public Mask getMask() {
        return mask;
    }

    /**
     * sets the mask of collisions
     * 
     * @param mask
     *            the mask of collisions to set
     */
    public void setMask(Mask mask) {
        this.mask = mask;
    }

    /**
     * creates the mask of collisions<br>
     * 
     */
    @Method("create and assign Mask")
    public void createMask() {
        new Mask(this);
    }

    // ------------------------------------------------------
    /**
     * sets the black\white texture to use to creates the transparent effect on
     * 2D scenes
     */
    @Method("set Alpha Mask")
    //
	public void setAlphaMask(@Info("Alpha Mask")
	ITexture texture) {
        this.alphaMask = texture;
    }

    /**
     * gets the black\white texture to use to creates the transparent effect on
     * 2D scenes
     */
    public ITexture getAlphaMask() {
        return alphaMask;
    }

    // ------------------------------------------------------

    /**
     * destroys the image and release all resources
     */
    @Method("Destroy object")
    //
    public void destroy() {
        if (destroyed) {
            return;
        }
        if (dataid != null) {
            if (--dataid.referenceCount == 0) {
                dataid.destroy();
            }
        }
        destroyed = true;
    }

    // public void finalize() {
    // destroyed = true;
    // if (OpenGLState.lastTextureID == getID()) {
    // OpenGLState.lastTextureID = -1;
    // }
    // }

    /**
     * returns if the texture is destroyed or not
     * 
     */
    public boolean isDestroyed() {
        return destroyed;
    }

    // ------------------------------------------------------
    public Texture[] split(int xOffset, int yOffset, int row, int coloumn, int width, int height, int spaceX, int spaceY) {
        Texture[] temp = new Texture[row * coloumn];
        for (int y = 0; y < row; ++y) {
            for (int x = 0; x < coloumn; ++x) {
                temp[x + y * coloumn] = new Texture(getTextureId());
                temp[x + y * coloumn].setRegion(xOffset + x * width + spaceX * x, yOffset + y * height + spaceY * y, width, height);
            }
        }
        return temp;
    }

    public Texture[][] split2D(int xstep[], int ystep[]) {
        if (xstep == null || ystep == null) {
            return null;
        }
        Texture texts[][] = new Texture[xstep.length][ystep.length];
        Texture ttext;

        float oldx, oldy, newW, newH;

        oldx = oldy = newH = 0;
        for (int y = 0; y < ystep.length; ++y) {
            oldy += newH;
            newH = (float) ystep[y] / (float) getHeightHW();
            oldx = 0;
            for (int x = 0; x < xstep.length; ++x) {
                newW = (float) xstep[x] / (float) getWidthHW();

                ttext = texts[x][y] = new Texture(this);

                ttext.width = xstep[x];
                ttext.height = ystep[y];
                ttext.xStart = oldx;
                ttext.xEnd = (oldx += newW);
                ttext.yStart = oldy;
                ttext.yEnd = oldy + newH;
            }
        }
        return texts;
    }

    public int getWidthHW() {
        return dataid.widthHW;
    }

    public int getHeightHW() {
        return dataid.heightHW;
    }

    public int getHeight() {
        return height;
    }

    public int getID() {
        return dataid.id;
    }

    public int getWidth() {
        return width;
    }

    public float getXEnd() {
        return xEnd;
    }

    public float getXStart() {
        return xStart;
    }

    public float getYEnd() {
        return yEnd;
    }

    public float getYStart() {
        return yStart;
    }

    public void setRegion(int x, int y, int width, int height) {
        if (x < 0 || x > getWidthHW()) {
            Utility.error("input error value: x", new Exception("error input value: x"));
            return;
        }
        if (y < 0 || y > getHeightHW()) {
            Utility.error("input error value: y", new Exception("error input value: y"));
            return;
        }
        if (width <= 0) {
            Utility.error("input error value: width", new Exception("error input value: width"));
            return;
        }
        if (height <= 0) {
            Utility.error("input error value: height", new Exception("error input value: height"));
            return;
        }
        if (width + x > getWidthHW()) {
            width = getWidthHW() - x;
        }
        if (height > getHeightHW()) {
            height = getHeightHW() - y;
        }

        xStart = (float)x / (float)getWidthHW();
        yStart = (float)y / (float)getHeightHW();
        xEnd = (float)(x + width) / (float)getWidthHW();
        yEnd = (float)(y + height) / (float)getHeightHW();
        this.width = width;
        this.height = height;
    }

    public String toString() {
        return name;
    }

	public static Texture readFromFile(ObjectInputStream in) {
        // ObjectInputStream in = Utility.getLocalFile(file);
        if (in == null) {
            return null;
        }
        try {
            // read the version (at this time it's only 1)
			/* int version = */ in.readInt();

            // fields:
            // there is the pathFileName field?
            boolean fromFile = in.readBoolean();
            if (fromFile) {
                // use file
                String path = (String) in.readObject();
                boolean useAlpha = !in.readBoolean();

                float startX = in.readFloat();
                float endX = in.readFloat();
                float startY = in.readFloat();
                float endY = in.readFloat();

                if (useAlpha) {
                    // use alpha
                    // TODO: note: if the alpha is in image-data this will
                    // create a BUG
                    byte red = in.readByte();
                    byte green = in.readByte();
                    byte blue = in.readByte();
                    // in.close();
                    return new Texture(path, red, green, blue);
                }
// image without alpha
// in.close();
                Texture t = new Texture(path);
                t.xStart = startX;
                t.xEnd = endX;
                t.yStart = startY;
                t.yEnd = endY;
                return t;

            } else {
                System.out.println("NOT FROM FILE O_O");
                // don't use file
                TextureID tid = (TextureID) in.readObject();
                // in.close();
                return new Texture(tid);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // in.close();
            e.printStackTrace();
        }

        return null;
    }

    public void writeOnFile(ObjectOutputStream out) {

        try {
            // FileOutputStream fout = new FileOutputStream(filename);
            // ObjectOutputStream out = new ObjectOutputStream(fout);

            // the version-id
            out.writeInt(2);

            if (dataid.pathFileName != null) {
                out.writeBoolean(true);
                out.writeObject(dataid.pathFileName);
                out.writeBoolean(dataid.isSolid());

                out.writeFloat(xStart);
                out.writeFloat(xEnd);
                out.writeFloat(yStart);
                out.writeFloat(yEnd);

                if (!dataid.isSolid()) {
                    out.writeByte(dataid.alphaRed);
                    out.writeByte(dataid.alphaGreen);
                    out.writeByte(dataid.alphaBlue);
                }

            } else {
                out.writeBoolean(false);
                out.writeObject(dataid);
            }

// out.close();
// fout.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
